# this file will contain the model representing the map. It will represent
# walls, cells and will provide methods to change walls and cells, and methods
# to copy or move parts of the map.

from TarjanDirection import Directions
from TarjanObservable import TarjanObservable


def _calc_wall_idx(direction, x, y, w):
    "returns the index of the 'direction'-wise wall of cell 'x', 'y' in a map 'w' cells wide"
    if   direction == Directions.north: return  2 * y      * w + x + y
    elif direction == Directions.east : return (2 * y + 1) * w + x + y + 1
    elif direction == Directions.south: return (2 * y + 2) * w + x + y + 1
    elif direction == Directions.west : return (2 * y + 1) * w + x + y
    raise ValueError("Invalid direction")

def _copy_map(src, dst, src_x, src_y, dst_x, dst_y, w, h):
    "copy an area from source map to destination map, at x/y coordinates and size w * h"
    for dy in range(h):
        # copy floors and notes
        src_idx = (src_y + dy) * src.width + src_x
        dst_idx = (dst_y + dy) * dst.width + dst_x
        dst.floors[ dst_idx : dst_idx + w ] = src.floors[ src_idx : src_idx + w ]
        dst.notes[ dst_idx : dst_idx + w ] = src.notes[ src_idx : src_idx + w ]
        # copy north walls
        src_north_idx = _calc_wall_idx(Directions.north, src_x, src_y + dy, src.width)
        dst_north_idx = _calc_wall_idx(Directions.north, dst_x, dst_y + dy, dst.width)
        dst.walls[ dst_north_idx : dst_north_idx + w ] = src.walls[ src_north_idx : src_north_idx + w ]
        # copy east / west walls
        src_west_idx = _calc_wall_idx(Directions.west, src_x, src_y + dy, src.width)
        dst_west_idx = _calc_wall_idx(Directions.west, dst_x, dst_y + dy, dst.width)
        dst.walls[ dst_west_idx : dst_west_idx + w + 1 ] = src.walls[ src_west_idx : src_west_idx + w + 1 ]
    # for last row,  copy south walls
    src_south_idx = _calc_wall_idx(Directions.south, src_x, src_y + dy, src.width)
    dst_south_idx = _calc_wall_idx(Directions.south, dst_x, dst_y + dy, dst.width)
    dst.walls[ dst_south_idx : dst_south_idx + w ] = src.walls[ src_south_idx : src_south_idx + w ]

def _wall_property(direction):
    def get(self, type=None): return self.getWall(direction)
    def set(self, value): self.setWall(direction, value)
    return property(fget=get, fset=set, doc='get / set the ' + str(direction) + ' wall')


class TarjanMap(TarjanObservable):
    "Tarjan Map model"

    floors = []
    notes = []
    walls = []
    cells = []
    width = 0
    height = 0
    tainted = False

    def __init__(self, width=10, height=10):
        TarjanObservable.__init__(self)
        self.width = width
        self.height = height
        self.floors = ["" for i in xrange(width * height)]
        self.notes = ["" for i in xrange(width * height)]
        self.walls = ["" for i in xrange(2 * width * height + width + height)]

    class Cell(object):
        "Tarjan Map model cell"
        def __init__(self, map, x, y):
            self.map = map
            self.x = x
            self.y = y

        def getWall(self, direction):
            "return the value of a wall"
            return self.map.walls[ _calc_wall_idx(direction, self.x, self.y, self.map.width) ]

        def setWall(self, direction, value):
            "set the value of a wall"
            self.map.walls[ _calc_wall_idx(direction, self.x, self.y, self.map.width) ] = value
            self.map.notify(self.x, self.y)

        def getFloor(self):
            "return the value of the cells floor"
            return self.map.floors[ self.y * self.map.width + self.x ]

        def setFloor(self, value):
            "set the value of the cells floor"
            self.map.floors[ self.y * self.map.width + self.x ] = value
            self.map.notify(self.x, self.y)

        def getNotes(self):
            return self.map.notes[ self.y * self.map.width + self.x ]

        def setNotes(self, value):
            self.map.notes[ self.y * self.map.width + self.x ] = value
            self.map.notify(self.x, self.y)

        north = _wall_property(Directions.north)
        east  = _wall_property(Directions.east)
        south = _wall_property(Directions.south)
        west  = _wall_property(Directions.west)

        floor = property( fget=getFloor, fset=setFloor, doc='get / set the floor'  )
        notes = property( fget=getNotes, fset=setNotes, doc='get / set the notes on this cell'  )

    def getCell(self, x, y):
        "get the cell at position x, y"
        return TarjanMap.Cell(self, x, y)

    def getMap(self, x, y, w, h):
        "return a new map, initalized with the values in this map at (x, y) and width / height (w, h)"
        copy = self.__class__(w, h)
        _copy_map(self, copy, x, y, 0, 0, w, h)
        return copy

    def setMap(self, map, x, y):
        "overwrite the map at (x, y) with the data from map. Uses width and height from map"
        _copy_map(map, self, 0, 0, x, y, map.width, map.height)
        self.notify(x, y, map.width, map.height)

    def clearMap(self, x, y, w, h):
        "clear the map in the rectacle defined by x, y / w, h"
        _copy_map(TarjanMap(w, h), self, 0, 0, x, y, w, h)
        self.notify(x, y, w, h)

    def notify(self, *args):
        self.tainted = True
        super(TarjanMap, self).notify(*args)

